﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace PddHelper.Framework
{
    /// <summary>
    /// 单例模式-泛型动态生成函数调用
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class DynamicMethod<T> where T : class, new()
    {
        private static readonly Lazy<DynamicMethod<T>> lazy = new Lazy<DynamicMethod<T>>(() => new DynamicMethod<T>());
        internal static DynamicMethod<T> Instance { get { return lazy.Value; } }

        private static Func<object, string, object> GetValueDelegate;
        private static Action<object, string, object> SetValueDelegate;

        internal PropertyInfo[] PropertyInfos;

        internal object GetValue(T instance, string memberName)
        {
            var member = this.PropertyInfos.FirstOrDefault(ex => ex.Name.ToLower() == memberName.ToLower());
            if (member != null)
            {
                var obj = GetValueDelegate(instance, member.Name);
                return obj;
            }
            return null;
        }

        internal void SetValue(T instance, string memberName, object newValue)
        {
            var member = this.PropertyInfos.FirstOrDefault(ex => ex.Name.ToLower() == memberName.ToLower());
            if (instance != null && member != null)
            {
                SetValueDelegate(instance, member.Name, newValue);
            }
        }

        private DynamicMethod()
        {
            this.PropertyInfos = typeof(T).GetProperties();

            GetValueDelegate = GenerateGetValue();
            SetValueDelegate = GenerateSetValue();
        }

        private Func<object, string, object> GenerateGetValue()
        {
            var instance = Expression.Parameter(typeof(object), "instance");
            var memberName = Expression.Parameter(typeof(string), "memberName");
            var nameHash = Expression.Variable(typeof(int), "nameHash");
            var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode")));
            var cases = new List<SwitchCase>();

            foreach (var propertyInfo in this.PropertyInfos)
            {
                var property = Expression.Property(Expression.Convert(instance, typeof(T)), propertyInfo.Name);
                var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int));

                cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash));
            }
            var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray());
            var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx);

            var obj = Expression.Lambda<Func<object, string, object>>(methodBody, instance, memberName).Compile();
            return obj;
        }

        private Action<object, string, object> GenerateSetValue()
        {
            var instance = Expression.Parameter(typeof(object), "instance");
            var memberName = Expression.Parameter(typeof(string), "memberName");
            var newValue = Expression.Parameter(typeof(object), "newValue");
            var nameHash = Expression.Variable(typeof(int), "nameHash");
            var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode")));
            var cases = new List<SwitchCase>();

            foreach (var propertyInfo in this.PropertyInfos)
            {
                var property = Expression.Property(Expression.Convert(instance, typeof(T)), propertyInfo.Name);
                if (propertyInfo.CanWrite)
                {
                    var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.PropertyType));
                    var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int));

                    cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash));
                }
            }
            var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray());
            var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx);

            var obj = Expression.Lambda<Action<object, string, object>>(methodBody, instance, memberName, newValue).Compile();
            return obj;
        }
    }
}
